//
// RS Game Framework
// Copyright © 2009 Jedd Haberstro
// jhaberstro@gmail.com
// 
// $Id:
//

#ifndef RS_SMARTPOINTER_HPP
#define RS_SMARTPOINTER_HPP

#include "rs/Assert.hpp"
#include <algorithm>

namespace rs
{
    template< typename T >
    class ReferencePolicy
    {
    public:
        
        static void Retain(T* pointer) {
            pointer->IncrementReference();
        }
        
        static void Release(T* pointer) {
            pointer->DecrementReference();
        }
    };
    
    template< typename T, template< typename > class TReferencePolicy = ReferencePolicy >
    class SmartPointer
    {
    public:
        
        typedef T ValueType;
        typedef T* PointerType;
        typedef T& ReferenceType;
        typedef TReferencePolicy< T > PolicyType;
        typedef SmartPointer< T, TReferencePolicy > ThisType;
        
    private:
        
        class NullTester
        {
            void operator delete(void*);
        };
        
    public:
        
        SmartPointer();
        
        explicit SmartPointer(T* pointer, Boolean addRef = true);

        SmartPointer(SmartPointer const& other);
        
        ~SmartPointer();
        
        template< typename U >
        SmartPointer(SmartPointer< U, TReferencePolicy > const& other);
        
        ThisType& operator=(ThisType const& other);
        
        template< typename U >
        ThisType& operator=(SmartPointer< U, TReferencePolicy > const& other);
        
        ThisType& operator=(PointerType pointer);
        
        Boolean operator!() const;
        
        operator NullTester*() const;
        
        PointerType operator->() const;
        
        ReferenceType operator*() const;
        
        PointerType GetPointer() const;
        
        void Reset(PointerType source = 0);
        
        void Swap(SmartPointer& other);
        
    private:
        
        T* pointer_;
    };
    
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline SmartPointer< T, TReferencePolicy >::SmartPointer()
    : pointer_(0) {
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline SmartPointer< T, TReferencePolicy >::SmartPointer(T* pointer, Boolean addRef)
    : pointer_(pointer) {
        if (pointer_ && addRef) {
            PolicyType::Retain(pointer_);
        }
    }

    template< typename T, template< typename TClass > class TReferencePolicy >
    inline SmartPointer< T, TReferencePolicy >::SmartPointer(SmartPointer const& other)
    : pointer_(other.pointer_) {
        if (pointer_) {
            PolicyType::Retain(pointer_);
        }
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    template< typename U >
    inline SmartPointer< T, TReferencePolicy >::SmartPointer(SmartPointer< U, TReferencePolicy > const& other)
    : pointer_(other.pointer_) {
        if (pointer_) {
            PolicyType::Retain(pointer_);
        }
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline SmartPointer< T, TReferencePolicy >::~SmartPointer() {
        if (pointer_ != 0) {
            PolicyType::Release(pointer_);
        }
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline typename SmartPointer< T, TReferencePolicy >::ThisType& SmartPointer< T, TReferencePolicy >::operator=(typename SmartPointer< T, TReferencePolicy >::ThisType const& other) {
        ThisType(other).Swap(*this);
        return *this;
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    template< typename U >
    inline typename SmartPointer< T, TReferencePolicy >::ThisType& SmartPointer< T, TReferencePolicy >::operator=(SmartPointer< U, TReferencePolicy > const& other) {
        ThisType(other).Swap(*this);
        return *this;
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline typename SmartPointer< T, TReferencePolicy >::ThisType& SmartPointer< T, TReferencePolicy >::operator=(PointerType pointer) {
        Reset(pointer);
        return *this;
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline Boolean SmartPointer< T, TReferencePolicy >::operator!() const {
        return !pointer_;
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline SmartPointer< T, TReferencePolicy >::operator NullTester*() const {
        if (!pointer_) {
            return 0;
        }
        
        static NullTester test;
        return &test;
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline typename SmartPointer< T, TReferencePolicy >::PointerType SmartPointer< T, TReferencePolicy >::operator->() const {
        RS_ASSERT(pointer_, "pointer_ is null");
        return pointer_;
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline typename SmartPointer< T, TReferencePolicy >::ReferenceType SmartPointer< T, TReferencePolicy >::operator*() const {
        RS_ASSERT(pointer_, "pointer_ is null");
        return *pointer_;
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline typename SmartPointer< T, TReferencePolicy >::PointerType SmartPointer< T, TReferencePolicy >::GetPointer() const {
        //RS_ASSERT(pointer_, "pointer_ is null");
        return pointer_;
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline void SmartPointer< T, TReferencePolicy >::Reset(PointerType source) {
        if (source) {
            ThisType(source).Swap(*this);
        }
        else {
            ThisType().Swap(*this);
        }
    }
    
    template< typename T, template< typename TClass > class TReferencePolicy >
    inline void SmartPointer< T, TReferencePolicy >::Swap(SmartPointer& other) {
        std::swap(pointer_, other.pointer_);
    }
        
    template< typename T, template< typename Class > class U, typename V >
    inline Boolean operator==(SmartPointer< T, U > const& lhs, V const* rhs) {
        return lhs.GetPointer() == rhs;
    }
    
    template< typename T, template< typename Class > class U, typename V >
    inline Boolean operator==(V const* lhs, SmartPointer< T, U > const& rhs) {
        return lhs == rhs.GetPointer();
    }
    
    template< typename T, template< typename Class > class U, typename V, template< typename Class > class W >
    inline Boolean operator==(SmartPointer< T, U > const& lhs, SmartPointer< V, W > const& rhs) {
        return lhs.GetPointer() == rhs.GetPointer();
    }
    
    template< typename T, template< typename Class > class U, typename V >
    inline Boolean operator!=(SmartPointer< T, U > const& lhs, V const* rhs) {
        return lhs.GetPointer() != rhs;
    }
    
    template< typename T, template< typename Class > class U, typename V >
    inline Boolean operator!=(V const* lhs, SmartPointer< T, U > const& rhs) {
        return lhs != rhs.GetPointer();
    }
    
    template< typename T, template< typename Class > class U, typename V, template< typename Class > class W >
    inline Boolean operator!=(SmartPointer< T, U > const& lhs, SmartPointer< V, W > const& rhs) {
        return lhs.GetPointer() != rhs.GetPointer();
    }
    
    // For storage in containers
    template< typename T, template< typename Class > class U, typename V, template< typename Class > class W >
    inline Boolean operator<(SmartPointer< T, U > const& lhs, SmartPointer< V, W > const& rhs) {
        return lhs.GetPointer() < rhs.GetPointer();
    }
}

#endif // RS_SMARTPOINTER_HPP
